package com.zhaowenhao.hello.spring.framework.webmvc;

import com.zhaowenhao.hello.spring.framework.annotation.*;
import jdk.nashorn.internal.runtime.linker.LinkerCallSite;
import lombok.SneakyThrows;
import lombok.val;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.swing.text.StyledEditorKit;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.*;

/**
 * @Description:
 * @Author: zwh
 * @Date: 2020-07-24
 * @Time: 16:43
 */
public class WHDispatcherServlet extends HttpServlet {

    //保存配置文件信息
    private Properties configContext = new Properties();

    //保存所有类名
    private List<String> className = new ArrayList<String>();

    //ioc容器
    private HashMap<String, Object> ioc = new HashMap<String, Object>();

    //handlerMapping
    private HashMap<String, Object> mapping = new HashMap<String, Object>();


    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("hello-------------------------------------------------");
        doPost(req, resp);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        try {
            doDispatch(req, resp);
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
    }

    private void doDispatch(HttpServletRequest req, HttpServletResponse resp) throws IOException, InvocationTargetException, IllegalAccessException {
        String uri = req.getRequestURI();
        String contextPath = req.getContextPath();
        uri = uri.replace(contextPath, "").replaceAll("/+", "/");
        if (!mapping.containsKey(uri)) {
            resp.getWriter().print("404!");
            return;
        }

        Map<String, String[]> parameterMap = req.getParameterMap();
        Method method = (Method) mapping.get(uri);

        //首先拿到形参列表
        Class<?>[] parameterTypes = method.getParameterTypes();
        Object[] params = new Object[parameterTypes.length];
        for (int i = 0; i < parameterTypes.length; i++) {
            Class<?> param = parameterTypes[i];
            if (param == HttpServletRequest.class) {
                params[i] = req;
                continue;
            } else if (param == HttpServletResponse.class) {
                params[i] = resp;
                continue;
            } else if (param == String.class) {
                Annotation[][] parameterAnnotations = method.getParameterAnnotations();
                for (Annotation annotation : parameterAnnotations[i]) {
                    if (annotation instanceof WHRequestParam) {
                        String paramName = ((WHRequestParam) annotation).value();
                        String value = Arrays.toString(parameterMap.get(paramName))
                                .replaceAll("\\[", "")
                                .replaceAll("\\]", "")
                                .replaceAll("\\s", "");
                        params[i] = value;
                    }
                }
            }
        }
        Object object = ioc.get(toLowerFirstCase(method.getDeclaringClass().getSimpleName()));
        method.invoke(object, params);
    }


    @SneakyThrows
    @Override
    public void init(ServletConfig config) throws ServletException {
        //加载配置文件
        doLoadConfig(config);

        //扫描配置类
        doScanPackage(configContext.getProperty("scanPackage"));

        //实例化,加入IOC中
        doInstance();

        //完成依赖注入
        doAutowired();

        //初始化HandlerMapping
        doInitHandlerMapping();

        System.out.println("WHSpring is init...");


    }

    private void doInitHandlerMapping() {
        if (ioc.isEmpty()) {
            return;
        }
        Set<Map.Entry<String, Object>> entries = ioc.entrySet();
        for (Map.Entry<String, Object> entry : entries) {
            Class<?> clazz = entry.getValue().getClass();
            String baseUrl = "";

            if (clazz.isAnnotationPresent(WHRequestMapping.class)) {
                WHRequestMapping annotation = clazz.getAnnotation(WHRequestMapping.class);
                baseUrl = annotation.value();
            }
            Method[] methods = clazz.getMethods();
            for (Method method : methods) {
                if (!method.isAnnotationPresent(WHRequestMapping.class)) {
                    continue;
                }
                WHRequestMapping whRequestMapping = method.getAnnotation(WHRequestMapping.class);
                String url = whRequestMapping.value();
                baseUrl = (baseUrl + "/" + url).replaceAll("/+", "/");
                mapping.put(baseUrl, method);
                System.out.println("Mapped" + baseUrl + "," + method);
            }

        }
    }

    private void doAutowired() throws IllegalAccessException {
        Collection<Object> values = ioc.values();
        for (Object object : values) {
            Field[] fields = object.getClass().getDeclaredFields();
            for (Field field : fields) {
                if (!field.isAnnotationPresent(WHAutowired.class)) {
                    continue;
                }
                WHAutowired autowired = field.getAnnotation(WHAutowired.class);
                String beanName = autowired.value().trim();
                if ("".equals(beanName)) {
                    beanName = field.getType().getName();
                }
                field.setAccessible(true);
                field.set(object, ioc.get(beanName));
            }
        }
    }

    private void doInstance() throws ClassNotFoundException, IllegalAccessException, InstantiationException {
        for (String className : className) {
            Class<?> clazz = Class.forName(className);
            //这里只实例化加了controller和service注解的类
            if (clazz.isAnnotationPresent(WHController.class)) {
                //装载到ioc
                Object instance = clazz.newInstance();
                String beanName = toLowerFirstCase(clazz.getSimpleName());
                ioc.put(beanName, instance);

            } else if (clazz.isAnnotationPresent(WHService.class)) {
                //遍历所有加了WHService注解的类，加入IOC中
                //1 使用自定义名字
                WHService service = clazz.getAnnotation(WHService.class);
                String beanName = service.value();

                //2 使用默认类名
                if ("".equals(beanName)) {
                    beanName = toLowerFirstCase(clazz.getSimpleName());
                }


                Object newInstance = clazz.newInstance();
                ioc.put(beanName, newInstance);

                //同时为所有接口赋予相应的实例对象
                Class<?>[] interfaces = clazz.getInterfaces();
                for (Class<?> inter : interfaces) {
                    ioc.put(inter.getName(), newInstance);
                }

            } else {
                continue;
            }
        }
    }

    private String toLowerFirstCase(String simpleName) {
        char[] chars = simpleName.toCharArray();
        chars[0] += 32;
        return String.valueOf(chars);
    }

    private void doLoadConfig(ServletConfig config) {
        //加载配置文件
        InputStream is = null;
        String configLocation = config.getInitParameter("contextConfigLocation");
        configLocation = configLocation.replace("classpath:", "/");

        is = this.getClass().getResourceAsStream(configLocation);
        try {
            configContext.load(is);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private void doScanPackage(String scanPackage) {
        URL url = this.getClass().getResource("/" + scanPackage.replaceAll("\\.", "/"));
        File file = new File(url.getFile());
        for (File f : file.listFiles()) {
            if (f.isDirectory()) {
                doScanPackage(scanPackage + "." + f.getName());
            } else {
                if (!f.getName().endsWith(".class")) {
                    continue;
                }
                String clazzName = scanPackage + "." + f.getName().replace(".class", "");
                className.add(clazzName);
            }
        }
    }
}
